﻿using System.ComponentModel;
using KadalaSpree.XCore;
using System;
using System.Collections.Generic;
using System.Linq;
using Zeta.Bot.Logic;
using Zeta.Common;
using Zeta.Game;
using Zeta.Game.Internals;
using Zeta.Game.Internals.Actors;
using Zeta.TreeSharp;
using Action = Zeta.TreeSharp.Action;

namespace KadalaSpree
{
    public class KadalaPlugin : XPlugin
    {
        private KadalaPlugin()
        {
            Name = "Kadala";
            Version = new Version(2, 3, 2);
            Author = "XZJV";
            Description = "Buys stuff from kadala when doing a town run";

            SettingsClass = KadalaSpreeSettings.Instance;
            SettingsXaml = "Kadala.Settings.xaml";

            Modules.Register(new Gamble(), this);
            Modules.Register(new TownRun(), this);
        }
    }

    public class Gamble : XModule
    {
        public static DateTime LastGambleTime = DateTime.MinValue;
        public static List<VendorSlotType> GambleRotation = new List<VendorSlotType>();
        public static bool IsGambling { get; set; }
        private static bool ShouldSpendAllShards { get; set; }

        public override void OnInitialize()
        {
            Name = "Gamble";

            X.Hooks.Register(this, CreateBehavior());

            if (KadalaSpreeSettings.Instance.InjectTownRun)
            {
                X.Hooks.Register(this, CreateBehavior(), HookType.VendorRun, HookMethod.Inject, 3);                
            }

            KadalaSpreeSettings.Instance.PropertyChanged += OnPropertyChanged;
        }

        internal static Composite CreateBehavior()
        {
            return
                new Decorator(ret => ShouldGamble,

                    new PrioritySelector(

                    X.ActionFailure(ret => IsGambling = true),

                    new Decorator(ret => !X.Actors.Kadala.Exists,
                        new Action(ret => X.Actors.Waypoint.MoveTo())
                        ),

                    new Decorator(ret => !X.Actors.Waypoint.Exists,
                        new Action(ret => X.Actors.Orek.MoveTo())
                        ),

                    new Decorator(ret => !X.Actors.Orek.Exists,
                        new Action(ret => X.Actors.Stash.MoveTo())
                        ),

                    new Decorator(ret => !X.Actors.Kadala.WithinInteractRange,
                        new Action(ret => X.Actors.Kadala.MoveTo())
                        ),

                    new Action(ret => X.Actors.Kadala.InteractUntil(a => UIElements.VendorWindow.IsValid && UIElements.VendorWindow.IsVisible)),

                    new Decorator(ret => ShouldBuyItems, new Action(ret => BuyItems())),

                    X.ActionFailure(ret => IsGambling = false)

                )
            );
        }

        /// <summary>
        /// Determines if gambling should start
        /// </summary>
        public static bool ShouldGamble
        {
            get
            {
                if (!ZetaDia.IsInTown || !X.Player.IsValid || ZetaDia.WorldType != Act.OpenWorld)
                    return false;

                if (!X.Inventory.IsFreeBackPackSlots)
                    return false;                    

                if (BrainBehavior.IsVendoring && !XVendor.IsEmptyBackpack)
                    return false;

                if (KadalaSpreeSettings.Instance.SelectedGambleSlots.Count <= 0)
                    return false;

                if (ZetaDia.CPlayer.BloodshardCount < KadalaSpreeSettings.Instance.MinimumBloodShards || !CanAffordMostExpensiveItem())
                {
                    ShouldSpendAllShards = false;
                    return false;                    
                }

                if (!ShouldSpendAllShards && KadalaSpreeSettings.Instance.ShouldSaveShards && ZetaDia.CPlayer.BloodshardCount <= KadalaSpreeSettings.Instance.SaveShardsThreshold)
                    return false;

                ShouldSpendAllShards = true;

                return true;
            }
        }

        public static bool CanAffordMostExpensiveItem()
        {
            var currentshards = ZetaDia.CPlayer.BloodshardCount;
            var result = true;
            KadalaSpreeSettings.Instance.SelectedGambleSlots.ForEach(s =>
            {
                int slotPrice;
                if (XLookup.Actors.Vendors.MysterySlotTypeAndPrice.TryGetValue(s, out slotPrice))
                {
                    if (currentshards < slotPrice)
                        result = false;

                    if (!result && UIElements.VendorWindow.IsVisible)
                        X.Logger.Instance.Log("Can't afford {0} Price={1} CurrentShards={2}", s, slotPrice, currentshards);

                }
            });



            return result;
        }

        /// <summary>
        /// Buy items from Kadala, this will loop until final status is reported, ensuring
        /// that DB doesnt interrupt and start salvage/sell/stash before we've filled up the backpack.
        /// <para>Returns Success when complete</para>
        /// </summary>
        private static RunStatus BuyItems()
        {
            if (!ShouldBuyItems)
            {
                BrainBehavior.ForceTownrun("Gambling finished");
                return RunStatus.Success;
            }

            if (!PurchaseDelayPassed)
                return RunStatus.Running;

            BuyNextItem();

            return RunStatus.Running;
        }

        /// <summary>
        /// Determines if we're supposed to buy items and are capable of buying them.
        /// </summary>
        private static bool ShouldBuyItems
        {
            get
            {
                if (!X.Player.IsValid || !ZetaDia.IsInTown)
                    return false;

                if (!XVendor.IsVendorWindowOpen)
                    return false;

                if (!X.Inventory.IsFreeBackPackSlots)
                    return false;

                if (ZetaDia.CPlayer.BloodshardCount < KadalaSpreeSettings.Instance.MinimumBloodShards || !CanAffordMostExpensiveItem())
                    return false;

                return true;
            }
        }

        /// <summary>
        /// Buy the next item
        /// </summary>
        internal static void BuyNextItem()
        {
            if (!GambleRotation.Any())
                GambleRotation = KadalaSpreeSettings.Instance.SelectedGambleSlots;

            var slot = GambleRotation[Helpers.Random.Next(GambleRotation.Count)];
            var dynamicId = X.Actors.GetMysteryItemBySlotType(slot).DynamicId;
            GambleRotation.Remove(slot);
            ZetaDia.Me.Inventory.BuyItem(dynamicId);
            Logger.Log("Buying: {0}", slot);
            LastGambleTime = DateTime.UtcNow;
        }

        /// <summary>
        /// Delay between gamble attempts has passed
        /// </summary>
        public static bool PurchaseDelayPassed
        {
            get
            {
                var timeSinceGamble = DateTime.UtcNow.Subtract(LastGambleTime).TotalMilliseconds;
                var randomDelayLength = Helpers.Random.Next(X.Settings.MinPurchaseDelay, X.Settings.MaxPurchaseDelay);
                return (LastGambleTime == DateTime.MinValue || timeSinceGamble > randomDelayLength);
            }
        }

        /// <summary>
        /// Update hooks to be inserted if "InjectTownRun" setting changes.
        /// </summary>
        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "InjectTownRun" && !KadalaSpreeSettings.Instance.InjectTownRun)
            {
                X.Hooks.Disable(Name, HookType.VendorRun);
                return;
            }
            X.Hooks.Enable(Name, HookType.VendorRun);
        }

    }

    public class TownRun : XModule
    {
        public override void OnInitialize()
        {
            Name = "Townrun";
            X.Hooks.Register(this, CreateBehavior());
        }

        public static bool IsOnTownRun { get; set; }

        public static Composite CreateBehavior()
        {
            return 
            new Sequence(

                new DecoratorContinue(ret => NotInTown,
                    new Action(ret => IsOnTownRun = false)                        
                ),

                // Take portal back to dungeon
                new DecoratorContinue(ret => ShouldPortalBack,
                    new Sequence(                        
                        new Action(ret => Logger.Log("Looks like we're done here, taking portal back")),
                        TakePortalBack()
                    )                       
                ),
                    
                // Force a townrun
                new Decorator(ret => ShouldForceTownRun,
                    new PrioritySelector(  
                        new Decorator(ret => !ZetaDia.IsInTown, new Sequence(
                            new Action(ret => PortalToTown()),
                            new Action(ret => IsOnTownRun = true)
                        ))
                    )
                )


            );
        }

        private static bool NotInTown
        {
            get { return !ZetaDia.IsInTown && !X.Player.IsTransitioning && !X.Player.IsCastingOrChanneling; }
        }

        private static bool ShouldPortalBack
        {
            get
            {
                // Not on a townrun
                if (!ZetaDia.IsInTown || !IsOnTownRun)
                    return false;

                // We still have stuff to do
                if (BrainBehavior.ShouldVendor || Gamble.ShouldGamble)
                    return false;
                
                // Other obvious reasons
                if (!StartedOutsideTown || X.Player.IsTransitioning || !X.Actors.HearthPortal.Exists)
                    return false;

                return true;
            }
        }

        private static bool ShouldForceTownRun
        {
            get
            {
                if (!ZetaDia.Me.IsValid && ZetaDia.CPlayer == null || !ZetaDia.CPlayer.IsValid)
                    return false;

                // Feature turned off or below threshhold
                if (!KadalaSpreeSettings.Instance.ShouldForceTownRun || ZetaDia.CPlayer.BloodshardCount < KadalaSpreeSettings.Instance.ForceTownRunThreshold)
                    return false;                     

                // Saving up shards
                if (KadalaSpreeSettings.Instance.ShouldSaveShards && ZetaDia.CPlayer.BloodshardCount < KadalaSpreeSettings.Instance.SaveShardsThreshold)
                    return false;                    

                // Below minimum
                if (ZetaDia.CPlayer.BloodshardCount < KadalaSpreeSettings.Instance.MinimumBloodShards || !Gamble.CanAffordMostExpensiveItem())
                    return false;

                return true;
            }
        }

        public static bool StartedOutsideTown { get; set; }
        private static DateTime LastPortalAttemptTime { get; set; }
        private static RunStatus PortalToTown()
        {
            var anim = X.Player.CurrentAnimationState;

            if (ZetaDia.IsInTown)
            {
                StartedOutsideTown = false;
                return RunStatus.Success;
            }

            // In Combat
            if (anim == AnimationState.TakingDamage || anim == AnimationState.Attacking || anim == AnimationState.Casting)
                return RunStatus.Success;

            // Channelling
            if (anim == AnimationState.Channeling || anim == AnimationState.Transform || anim.ToString() == "13")
                return RunStatus.Success;

            // Too soon
            if (DateTime.UtcNow.Subtract(LastPortalAttemptTime).TotalMilliseconds < 1000)
                return RunStatus.Success;

            UIElements.BackgroundScreenPCButtonRecall.Click();
            LastPortalAttemptTime = DateTime.UtcNow;
            StartedOutsideTown = true;

            return RunStatus.Success;
        }

        private static Composite TakePortalBack()
        {
            return
            new PrioritySelector(
                new Action(ret => X.Actors.HearthPortal.MoveTo()),
                new Action(ret => X.Actors.HearthPortal.InteractUntil(a => !ZetaDia.IsInTown))
            );
        }

    }

}